using System;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Threading;
using System.Linq;
using System.Xml;
using System.Runtime.Serialization.Json;
using System.Text.RegularExpressions;
using System.Xml.Serialization;
using System.Web;
using System.Diagnostics;
using System.Text;
using LinqToTwitter;
using Kerr;

namespace System.Net
{
    #region Delegates
    public delegate void CommandReceived(string IrcCommand);
    public delegate void TopicSet(string IrcChannel, string IrcTopic);
    public delegate void TopicOwner(string IrcChannel, string IrcUser, string TopicDate);
    public delegate void NamesList(string UserNames);
    public delegate void ServerMessage(string ServerMessage);
    public delegate void Join(string IrcChannel, string IrcUser);
    public delegate void Part(string IrcChannel, string IrcUser);
    public delegate void Mode(string IrcChannel, string IrcUser, string UserMode);
    public delegate void NickChange(string UserOldNick, string UserNewNick);
    public delegate void Kick(string IrcChannel, string UserKicker, string UserKicked, string KickMessage);
    public delegate void Quit(string UserQuit, string QuitMessage);
    #endregion

    public class IRC
    {
        #region Events
        public event CommandReceived eventReceiving;
        public event TopicSet eventTopicSet;
        public event TopicOwner eventTopicOwner;
        public event NamesList eventNamesList;
        public event ServerMessage eventServerMessage;
        public event Join eventJoin;
        public event Part eventPart;
        public event Mode eventMode;
        public event NickChange eventNickChange;
        public event Kick eventKick;
        public event Quit eventQuit;
        #endregion

        #region Private Variables
        private string ircServer;
        private int ircPort;
        private string ircNick;
        private string ircUser;
        private string ircRealName;
        private string ircChannel;
        private string masterUser;
        private bool isInvisible;
        private TcpClient ircConnection;
        private NetworkStream ircStream;
        private StreamWriter ircWriter;
        private StreamReader ircReader;
        private string LastTwitID;
        private Regex urlRegex = new Regex(@"http\S+", RegexOptions.Compiled);
        Thread cricT = null;
        #endregion

        #region Properties
        public string IrcServer
        {
            get { return this.ircServer; }
            set { this.ircServer = value; }
        } /* IrcServer */

        public int IrcPort
        {
            get { return this.ircPort; }
            set { this.ircPort = value; }
        } /* IrcPort */

        public string IrcNick
        {
            get { return this.ircNick; }
            set { this.ircNick = value; }
        } /* IrcNick */

        public string IrcUser
        {
            get { return this.ircUser; }
            set { this.ircUser = value; }
        } /* IrcUser */

        public string IrcRealName
        {
            get { return this.ircRealName; }
            set { this.ircRealName = value; }
        } /* IrcRealName */

        public string IrcChannel
        {
            get { return this.ircChannel; }
            set { this.ircChannel = value; }
        } /* IrcChannel */

        /// <summary>
        /// Gets or sets the master user for which the bot obeys the command.
        /// </summary>
        /// <value>The master user.</value>
        public string MasterUser
        {
            get { return (String.IsNullOrEmpty(masterUser) ? "planetcall" : masterUser); }
            set
            {
                masterUser = value;
            }
        }


        public bool IsInvisble
        {
            get { return this.isInvisible; }
            set { this.isInvisible = value; }
        } /* IsInvisible */

        public TcpClient IrcConnection
        {
            get { return this.ircConnection; }
            set { this.ircConnection = value; }
        } /* IrcConnection */

        public NetworkStream IrcStream
        {
            get { return this.ircStream; }
            set { this.ircStream = value; }
        } /* IrcStream */

        public StreamWriter IrcWriter
        {
            get { return this.ircWriter; }
            set { this.ircWriter = value; }
        } /* IrcWriter */

        public StreamReader IrcReader
        {
            get { return this.ircReader; }
            set { this.ircReader = value; }
        } /* IrcReader */
        #endregion

        #region Constructor
        public IRC(string IrcNick, string IrcChannel)
        {
            this.IrcNick = IrcNick;
            this.IrcUser = "nHelp";
            this.IrcRealName = "nHelp (modded version of cIRC v1.0)";
            this.IrcChannel = IrcChannel;
            this.IsInvisble = false;
        } /* IRC */
        #endregion

        #region Public Methods
        public void Connect(string IrcServer, int IrcPort)
        {
            this.IrcServer = IrcServer;
            this.IrcPort = IrcPort;

            // Connect with the IRC server.
            this.IrcConnection = new TcpClient(this.IrcServer, this.IrcPort);
            this.IrcStream = this.IrcConnection.GetStream();
            this.IrcReader = new StreamReader(this.IrcStream);
            this.IrcWriter = new StreamWriter(this.IrcStream);

            // Authenticate our user
            string isInvisible = this.IsInvisble ? "8" : "0";
            this.IrcWriter.WriteLine(String.Format("USER {0} {1} * :{2}", this.IrcUser, isInvisible, this.IrcRealName));
            this.IrcWriter.Flush();
            this.IrcWriter.WriteLine(String.Format("NICK {0}", this.IrcNick));
            this.IrcWriter.Flush();
            this.IrcWriter.WriteLine(String.Format("JOIN {0}", this.IrcChannel));
            this.IrcWriter.Flush();

            // Listen for commands
            while (true)
            {
                string ircCommand;
                while ((ircCommand = this.IrcReader.ReadLine()) != null)
                {
                    Debug.WriteLine("IRC: " + ircCommand);
                    if (eventReceiving != null) { this.eventReceiving(ircCommand); }

                    string[] commandParts = new string[ircCommand.Split(' ').Length];
                    commandParts = ircCommand.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                    if (commandParts[0].Substring(0, 1) == ":")
                    {
                        commandParts[0] = commandParts[0].Remove(0, 1);
                    }

                    if (commandParts[0] == this.IrcServer)
                    {
                        // Server message
                        switch (commandParts[1])
                        {
                            case "332": this.IrcTopic(commandParts); break;
                            case "333": this.IrcTopicOwner(commandParts); break;
                            case "353": this.IrcNamesList(commandParts); break;
                            case "366": /*this.IrcEndNamesList(commandParts);*/ break;
                            case "372": /*this.IrcMOTD(commandParts);*/ break;
                            case "376": /*this.IrcEndMOTD(commandParts);*/ break;
                            default: this.IrcServerMessage(commandParts); break;
                        }
                    }
                    else if (commandParts[0] == "PING")
                    {
                        // Server PING, send PONG back
                        this.IrcPing(commandParts);
                    }
                    else
                    {
                        // Normal message
                        string commandAction = commandParts[1];
                        switch (commandAction)
                        {
                            case "JOIN": this.IrcJoin(commandParts); break;
                            case "PART": this.IrcPart(commandParts); break;
                            case "MODE": this.IrcMode(commandParts); break;
                            case "NICK": this.IrcNickChange(commandParts); break;
                            case "KICK": this.IrcKick(commandParts); break;
                            case "QUIT": this.IrcQuit(commandParts); break;
                            case "PRIVMSG":
                                {
                                    try
                                    {
                                        if (!(commandParts[0].StartsWith(@"planetcall")
                                            && ircCommand.Contains(this.IrcNick)))
                                            break;
                                        string statement;
                                        if(ircCommand.Contains("~cricket"))
                                        {
                                            if (cricT != null)
                                                break;
                                             cricT = new Thread(StartCricketUpdates);
                                             cricT.Start();

                                        }
                                        else
                                            if(ircCommand.Contains("~score"))
                                        {
                                            if (cricT == null)
                                                break;
                                            string update = null;
                                            this.UpdateCricketScore(out update);
                                            if (!string.IsNullOrEmpty(update))
                                                this.BotSpeak(update);
                                        }
                                    else
                                        if (ircCommand.Contains("~time"))
                                        {
                                            statement = String.Format("The time is {0} {1}",
                                                DateTime.Now.ToString("MMMM dddd yyyy HH:mm:ss.ffff zzz "),
                                                TimeZone.CurrentTimeZone.StandardName);
                                            BotSpeak(statement);
                                            break;
                                        }
                                        else
                                            if (commandParts.Length > 4)
                                            {
                                                if (commandParts[4].StartsWith("~google"))
                                                {
                                                    statement = Regex.Match(ircCommand, @"~google\s+(?<data>.+)").Groups["data"].ToString();
                                                    if (!string.IsNullOrEmpty(statement))
                                                        if (statement.Trim().Length > 50)
                                                        {
                                                            BotSpeak("Can not search for string of more than 50 characters");
                                                        }
                                                        else
                                                            GoogleSearch(statement);
                                                    else
                                                        BotSpeak("Nothing to search");
                                                    break;
                                                }
                                                if (commandParts[4].StartsWith("~weather"))
                                                {
                                                    statement = Regex.Match(ircCommand, @"~weather\s+(?<data>.+)").Groups["data"].ToString();
                                                    if (statement.Trim().Length < 30)
                                                        WeatherSearch(statement);
                                                    else
                                                        BotSpeak("Invalid City, Country");
                                                    break;
                                                }
                                                if (commandParts[4].StartsWith("~book"))
                                                {
                                                    statement = Regex.Match(ircCommand, @"~book\s+(?<data>.+)").Groups["data"].ToString();
                                                    if (statement.Trim().Length < 50)
                                                        BookSearch(statement);
                                                    else
                                                        BotSpeak("Invalid book information");
                                                    break;
                                                }

                                            }
                                        if (commandParts[3].StartsWith(":~tell"))
                                        {
                                            string user = commandParts[4].StartsWith("!") ? commandParts[4].Remove(0, 1) : "";
                                            //example-   tell !someircuser hi                                                    
                                            statement = Regex.Match(ircCommand, @"~tell\s+(!\S+)?(?<data>.+)").Groups["data"].ToString();
                                            if (string.IsNullOrEmpty(user))
                                                BotSpeak(statement);
                                            else
                                                BotSpeak(user, statement);
                                            break;
                                        }
                                        else
                                            if (ircCommand.Contains("~identify"))
                                                BotSpeak(String.Format("I am {0} v0.1 pre-alpha. My master is {1}", this.IrcNick, this.MasterUser));
                                        break;
                                    }
                                    catch (Exception ex)
                                    {
                                        BotSpeak("Ohh! some error happened which says " + ex.Message);
                                        break;
                                    }
                                }
                        }
                    }
                }

                this.IrcWriter.Close();
                this.IrcReader.Close();
                this.IrcConnection.Close();
            }
        } /* Connect */
        #endregion

        #region Private Methods
        #region Server Messages
        private void IrcTopic(string[] IrcCommand)
        {
            string IrcChannel = IrcCommand[3];
            string IrcTopic = "";
            for (int intI = 4; intI < IrcCommand.Length; intI++)
            {
                IrcTopic += IrcCommand[intI] + " ";
            }
            if (eventTopicSet != null) { this.eventTopicSet(IrcChannel, IrcTopic.Remove(0, 1).Trim()); }
        } /* IrcTopic */

        private void IrcTopicOwner(string[] IrcCommand)
        {
            string IrcChannel = IrcCommand[3];
            string IrcUser = IrcCommand[4].Split('!')[0];
            string TopicDate = IrcCommand[5];
            if (eventTopicOwner != null) { this.eventTopicOwner(IrcChannel, IrcUser, TopicDate); }
        } /* IrcTopicOwner */

        private void IrcNamesList(string[] IrcCommand)
        {
            string UserNames = "";
            for (int intI = 5; intI < IrcCommand.Length; intI++)
            {
                UserNames += IrcCommand[intI] + " ";
            }
            if (eventNamesList != null) { this.eventNamesList(UserNames.Remove(0, 1).Trim()); }
        } /* IrcNamesList */

        private void IrcServerMessage(string[] IrcCommand)
        {
            string ServerMessage = "";
            for (int intI = 1; intI < IrcCommand.Length; intI++)
            {
                ServerMessage += IrcCommand[intI] + " ";
            }
            if (eventServerMessage != null) { this.eventServerMessage(ServerMessage.Trim()); }
        } /* IrcServerMessage */
        #endregion

        #region Ping
        private void IrcPing(string[] IrcCommand)
        {
            string PingHash = "";
            for (int intI = 1; intI < IrcCommand.Length; intI++)
            {
                PingHash += IrcCommand[intI] + " ";
            }
            this.IrcWriter.WriteLine("PONG " + PingHash);
            this.IrcWriter.Flush();
        } /* IrcPing */
        #endregion

        #region User Messages
        private void IrcJoin(string[] IrcCommand)
        {
            string IrcChannel = IrcCommand[2];
            string IrcUser = IrcCommand[0].Split('!')[0];
            if (eventJoin != null) { this.eventJoin(IrcChannel.Remove(0, 1), IrcUser); }
        } /* IrcJoin */

        private void IrcPart(string[] IrcCommand)
        {
            string IrcChannel = IrcCommand[2];
            string IrcUser = IrcCommand[0].Split('!')[0];
            if (eventPart != null) { this.eventPart(IrcChannel, IrcUser); }
        } /* IrcPart */

        private void IrcMode(string[] IrcCommand)
        {
            string IrcChannel = IrcCommand[2];
            string IrcUser = IrcCommand[0].Split('!')[0];
            string UserMode = "";
            for (int intI = 3; intI < IrcCommand.Length; intI++)
            {
                UserMode += IrcCommand[intI] + " ";
            }
            if (UserMode.Substring(0, 1) == ":")
            {
                UserMode = UserMode.Remove(0, 1);
            }
            if (eventMode != null) { this.eventMode(IrcChannel, IrcUser, UserMode.Trim()); }
        } /* IrcMode */

        private void IrcNickChange(string[] IrcCommand)
        {
            string UserOldNick = IrcCommand[0].Split('!')[0];
            string UserNewNick = IrcCommand[2].Remove(0, 1);
            if (eventNickChange != null) { this.eventNickChange(UserOldNick, UserNewNick); }
        } /* IrcNickChange */

        private void IrcKick(string[] IrcCommand)
        {
            string UserKicker = IrcCommand[0].Split('!')[0];
            string UserKicked = IrcCommand[3];
            string IrcChannel = IrcCommand[2];
            string KickMessage = "";
            for (int intI = 4; intI < IrcCommand.Length; intI++)
            {
                KickMessage += IrcCommand[intI] + " ";
            }
            if (eventKick != null) { this.eventKick(IrcChannel, UserKicker, UserKicked, KickMessage.Remove(0, 1).Trim()); }
        } /* IrcKick */

        private void IrcQuit(string[] IrcCommand)
        {
            string UserQuit = IrcCommand[0].Split('!')[0];
            string QuitMessage = "";
            for (int intI = 2; intI < IrcCommand.Length; intI++)
            {
                QuitMessage += IrcCommand[intI] + " ";
            }
            if (eventQuit != null) { this.eventQuit(UserQuit, QuitMessage.Remove(0, 1).Trim()); }
        } /* IrcQuit */
        #endregion

        #region BotFeatures
        private void BotSpeak(string message)
        {
            BotSpeak(String.Empty, message);
        }

        private void BotSpeak(string user, string message)
        {

            if (String.IsNullOrEmpty(message))
            {
                Console.WriteLine("Error: Message is null or empty");
                return;
            }
            else if(message.Length > 1000)
            {
                Console.WriteLine("Error: Message Length exceeded 1000 characters");
                return;
            }
            message = message.Trim();
            int currCharIndex = 0;
            int charCountLimit = 450;
            int messageLength = message.Length;
            while (currCharIndex < messageLength)
            {
                charCountLimit = (currCharIndex + charCountLimit < messageLength) ?
                charCountLimit :
                messageLength - currCharIndex;
                string msg = message.Substring(currCharIndex, charCountLimit);
                if (String.IsNullOrEmpty(user))
                {
                    Console.WriteLine("Sending: {0}", msg);
                    this.IrcWriter.WriteLine("PRIVMSG {0} :{1}", this.IrcChannel, msg);
                }
                else
                {
                    Console.WriteLine("Sending to {0}, {1}", user, msg);
                    this.IrcWriter.WriteLine(String.Format(@"PRIVMSG {0} :{2}ACTION {1}! {2}", user, msg,(char)(001)));
                }
                this.IrcWriter.Flush();
                currCharIndex += charCountLimit;
            }
        }

        private void GoogleSearch(string keyword)
        {
            System.Net.WebRequest httpWebRequest =
                HttpWebRequest.Create(String.Format("http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q={0}",
                keyword));
            WebResponse webResponse = httpWebRequest.GetResponse();
            //System.IO.StreamReader sReader = new System.IO.StreamReader(webResponse.GetResponseStream());
            //string response = sReader.ReadToEnd();
            XmlDictionaryReader xdr =
                JsonReaderWriterFactory.CreateJsonReader
                (webResponse.GetResponseStream(),
                XmlDictionaryReaderQuotas.Max);
            XmlDocument xDoc = new XmlDocument();
            xDoc.Load(xdr);
            System.Text.StringBuilder searchRes = new System.Text.StringBuilder();
            int counter = 0;
            do
            {
                if (xDoc.GetElementsByTagName("item")[counter] != null)
                {
                    searchRes.AppendFormat("{2}{0} @ {1}",
                        xDoc.GetElementsByTagName("item")[counter].SelectSingleNode("titleNoFormatting").InnerText,
                        xDoc.GetElementsByTagName("item")[counter].SelectSingleNode("unescapedUrl").InnerText,
                    counter > 0 ? " | " : " ");
                }
                else
                    break;
            } while (counter++ < 3);
            if (!String.IsNullOrEmpty(searchRes.ToString()))
                BotSpeak(searchRes.ToString());
            else
                BotSpeak("No results found for keyword");
            return;
        }

        private void WeatherSearch(string keyword)
        {
            const string WEATHER_URL = "http://api.wunderground.com/auto/wui/geo/WXCurrentObXML/index.xml?query={0}";
            string response = string.Empty;
            try
            {
                System.Net.WebRequest httpWebRequest =
                    HttpWebRequest.Create(String.Format(WEATHER_URL,
                    keyword));
                WebResponse webResponse = httpWebRequest.GetResponse();
                response = new System.IO.StreamReader(webResponse.GetResponseStream()).ReadToEnd();
                XmlSerializer xDes = new XmlSerializer(typeof(current_observation));
                current_observation weather = xDes.Deserialize(new System.IO.StringReader(response)) as current_observation;
                System.Text.StringBuilder searchRes = new System.Text.StringBuilder();
                searchRes.AppendFormat(@"{0} ({1} Latitude/{2} Longitude); {3}; Conditions: {4}; Temperature: {5}; Humidity: {6}; Wind: {7}",
                    weather.display_location[0].full,
                    double.Parse(weather.display_location[0].latitude).ToString("N"),
                    double.Parse(weather.display_location[0].longitude).ToString("N"),
                    weather.observation_time,
                    weather.weather,
                    weather.temperature_string,
                    weather.relative_humidity,
                    weather.wind_string);
                response = searchRes.ToString();
            }
            catch (Exception ex)
            {
                response = "No data available";
            }
            BotSpeak(response);
        }

        private void BookSearch(string keyword)
        {
            const string GOOGLE_BOOK_SEARCH = "http://books.google.com/books/feeds/volumes?q={0}&max-results=3&prettyprint=true&alt=rss";
            System.Net.WebRequest httpWebRequest =
                HttpWebRequest.Create(String.Format(GOOGLE_BOOK_SEARCH, HttpUtility.UrlEncode(keyword)));
            WebResponse webResponse = httpWebRequest.GetResponse();
            XmlDocument xDoc = new XmlDocument();
            xDoc.Load(webResponse.GetResponseStream());
            XmlNamespaceManager nsMan = new XmlNamespaceManager(xDoc.NameTable);
            nsMan.AddNamespace("dc", "http://purl.org/dc/terms");
            System.Text.StringBuilder searchRes = new System.Text.StringBuilder();
            int counter = 0;
            do
            {
                if (xDoc.GetElementsByTagName("item")[counter] != null)
                {
                    string title = xDoc.GetElementsByTagName("item")[counter].SelectSingleNode("title").InnerText;
                    string authors = string.Empty;
                    XmlNodeList xAuthors = xDoc.SelectNodes(string.Format(@"//item[{0}]/dc:creator", counter + 1), nsMan);
                    foreach (XmlNode authorNode in xAuthors)
                    {
                        authors += authorNode.InnerText + ((xAuthors.Count > 0) ? "," : "");
                    }
                    authors = authors.TrimEnd(',');
                    string publishers = string.Empty;
                    XmlNodeList xPublishers = xDoc.SelectNodes(string.Format(@"//item[{0}]/dc:publisher", counter + 1), nsMan);
                    foreach (XmlNode publisherNode in xPublishers)
                    {
                        publishers += publisherNode.InnerText + ((xPublishers.Count > 0) ? "," : "");
                    }
                    publishers = publishers.TrimEnd(',');
                    string date = (xDoc.GetElementsByTagName("item")[counter].SelectSingleNode("dc:date", nsMan) == null) ? "" :
                            xDoc.GetElementsByTagName("item")[counter].SelectSingleNode("dc:date", nsMan).InnerText;
                    string format = (xDoc.GetElementsByTagName("item")[counter].SelectSingleNode("dc:format", nsMan) == null) ? "" :
                            xDoc.GetElementsByTagName("item")[counter].SelectSingleNode("dc:format", nsMan).InnerText;
                    string identifier = (xDoc.GetElementsByTagName("item")[counter].SelectSingleNode("dc:identifier[2]", nsMan) == null) ? "" :
                            xDoc.GetElementsByTagName("item")[counter].SelectSingleNode("dc:identifier[2]", nsMan).InnerText;
                    string link = (xDoc.GetElementsByTagName("item")[counter].SelectSingleNode("link") == null) ? "" :
                        xDoc.GetElementsByTagName("item")[counter].SelectSingleNode("link").InnerText;
                    searchRes.AppendFormat(" {7} {0} by {1} from {2}({3}, {4}) {5} {6}",
                        title,
                        authors,
                        publishers,
                        date,
                        format,
                        identifier,
                        link,
                    counter > 0 ? "|" : "");
                }
                else
                    break;
            } while (counter++ < 3);
            BotSpeak(searchRes.ToString());
        }

        private void StartCricketUpdates()
        {
            string oldmsg = null;
            while (true)
            {
                UpdateCricketScore(out oldmsg);
                Thread.Sleep(60000);
            }
        }

         private void UpdateCricketScore(out string updateMsg)
         {
             //var screenName = "vCricketIN";
             var screenName = "testcricket";
             updateMsg = null;
             var feedMsg = new StringBuilder(string.Empty);
             var tweet = GetTweetForUser(screenName);
             if (tweet.StatusID == this.LastTwitID)
                 updateMsg = String.Format("Nothing to update. Last update was: {0}", tweet.Text);
             else
             {
                 LastTwitID = tweet.StatusID;
                 BotSpeak(tweet.Text);
             }
         }

        public Status GetTweetForUser(string screenName)
        {
            TwitterContext twitterCtx = new TwitterContext();

            var statusTweets =
                from tweet in twitterCtx.Status
                where tweet.Type == StatusType.User
                      && tweet.ScreenName == screenName
                      && tweet.Count == 1
                select tweet;

            var twit = statusTweets.First();
            twit.Text = urlRegex.Replace(twit.Text, string.Empty);
            return twit;

            //foreach (var tweet in statusTweets)
            //{
            //    Console.WriteLine(
            //        "(" + tweet.StatusID + ")" +
            //        "[" + tweet.User.ID + "]" +
            //        tweet.User.Name + ", " +
            //        tweet.Text + ", " +
            //        tweet.CreatedAt);
            //}
        }

        #endregion
        #endregion
    } /* IRC */
} /* System.Net */